package dosgi.core.hook;

import java.lang.reflect.Field;
import java.util.Collection;
import java.util.Dictionary;
import java.util.List;

import org.eclipse.osgi.internal.serviceregistry.ShrinkableCollection;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceReference;
import org.osgi.framework.ServiceRegistration;
import org.osgi.framework.hooks.service.FindHook;
import org.osgi.service.log.LogService;

import dbconnector.service.IServiceInfo;
import dosgi.core.Activator;
import dosgi.core.proxy.ProxyCreator;
import dosgi.core.proxy.invocationhandler.MoMInvocationHandler;
import dosgi.core.util.DatabaseProxy;
import dosgi.core.util.LogHelper;

/**
 * Class defining service which will be called during framework service find (get service references) operations. 
 * 
 * @author Jaroslaw Janik
 */
public class GetServiceHook implements FindHook {

	private LogService logger = LogHelper.getLogger();
	private DatabaseProxy databaseProxy;
	
	public GetServiceHook(DatabaseProxy databaseProxy) {
		this.databaseProxy = databaseProxy;
	}
	
	/**
	 * Method which is called during the service find operation (for example, BundleContext.getServiceReferences(String, String) ). 
	 * This method can filter the result of the find operation. 
	 */
	@Override
	public void find(BundleContext context, String name, String filter,
			boolean allServices, Collection<ServiceReference<?>> references) {
		
		if (filter == null || !isRemote(filter)) {
			List<IServiceInfo> services = databaseProxy.getService(name);
			
			if (services != null) {
				for (IServiceInfo service : services) {
					try {
						ClassLoader myClassClassLoader = Activator.class.getClassLoader();
						Class<?> clazz = myClassClassLoader.loadClass(name);
						
						ServiceRegistration<?> reg = registerProxy(context, clazz, service);
						addToReferences(reg.getReference(), references);
					} catch (ClassNotFoundException e) {
						logger.log(LogService.LOG_ERROR, "Class not found", e);
					}
				}
			}
		}
	}
	
	private boolean isRemote(String filter) {
		return filter.contains(HooksManager.REMOTE_PROPERTY_KEY + "=" + HooksManager.REMOTE_PROPERTY_VALUE_TRUE);
	}
	
	private ServiceRegistration<?> registerProxy(BundleContext context, Class<?> clazz, IServiceInfo service) {
		Object object = ProxyCreator.newInstance(clazz, new MoMInvocationHandler(clazz, context, service.getQueueName()));
		return context.registerService(clazz.getName(), object, prepareProperties(service.getProperties()));
	}
	
	@SuppressWarnings({ "rawtypes", "restriction", "unchecked" })
	private void addToReferences(ServiceReference<?> reference, Collection<ServiceReference<?>> references) {
		try {
	        Field field = ShrinkableCollection.class.getDeclaredField("collection");
	        
			field.setAccessible(true);
			
	        Collection collection = (Collection) field.get(references);
	        collection.add(reference);
	        
	        field.setAccessible(false);
		} catch (NoSuchFieldException e) {
			logger.log(LogService.LOG_ERROR, "Cannot add service", e);
		} catch (SecurityException e) {
			logger.log(LogService.LOG_ERROR, "Cannot add service", e);
		} catch (IllegalArgumentException e) {
			logger.log(LogService.LOG_ERROR, "Cannot add service", e);
		} catch (IllegalAccessException e) {
			logger.log(LogService.LOG_ERROR, "Cannot add service", e);
		}        
	}
	
	private Dictionary<String, String> prepareProperties(Dictionary<String, String> properties) {
		removeRemoteProperty(properties);
		putProxyProperty(properties);
		
		return properties;
	}
	
	private void removeRemoteProperty(Dictionary<String, String> properties) {
		String value = properties.get(HooksManager.REMOTE_PROPERTY_KEY);
		
		if (value != null) {
			properties.remove(HooksManager.REMOTE_PROPERTY_KEY);
		}
	}
	
	private void putProxyProperty(Dictionary<String, String> properties) {
		properties.put(HooksManager.PROXY_PROPERTY_KEY, HooksManager.PROXY_PROPERTY_VALUE_TRUE);
	}
}
